/* *
 * --ライセンスについて--
 *
 * 「本ファイルの内容は Mozilla Public License Version 1.1 (「本ライセンス」)
 * の適用を受けます。
 * 本ライセンスに従わない限り本ファイルを使用することはできません。
 * 本ライセンスのコピーは http://www.mozilla.org/MPL/ から入手できます。
 *
 * 本ライセンスに基づき配布されるソフトウェアは、「現状のまま」で配布されるものであり、
 * 明示的か黙示的かを問わず、いかなる種類の保証も行われません。
 * 本ライセンス上の権利および制限を定める具体的な文言は、本ライセンスを参照してください。
 *
 * オリジナルコードおよび初期開発者は、N_H (h.10x64@gmail.com) です。
 *
 * N_H によって作成された部分の著作権表示は次のとおりです。
 *
 * Copyright (C) 2011 - 2012
 *
 * このファイルの内容は、上記に代えて、
 * GNU General License version2 以降 (以下 GPL とする)、
 * GNU Lesser General Public License Version 2.1 以降 (以下 LGPL とする)、
 * の条件に従って使用することも可能です。
 * この場合、このファイルの使用には上記の条項ではなく GPL または LGPL の条項が適用されます。
 * このファイルの他者による使用を GPL または LGPL の条件によってのみ許可し、
 * MPL による使用を許可したくない対象者は、上記の条項を削除することでその意思を示し、
 * 上記条項を GPL または LGPL で義務付けられている告知およびその他の条項に置き換えてください。
 * 対象者が上記の条項を削除しない場合、
 * 受領者は MPL または GPL または LGPL ライセンスのいずれによってもこのファイルを
 * 使用することができます。」
 *
 * -- License --
 *
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License。You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND、either express or implied。See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Initial Developer of the Original Code is
 *   N_H (h.10x64@gmail.com).
 *
 * Portions created by the Initial Developer are Copyright (C) 2011 - 2012
 * the Initial Developer。All Rights Reserved.
 *
 * Alternatively、the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL")、or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above。If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL、and not to allow others to
 * use your version of this file under the terms of the MPL、indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL。If you do not delete
 * the provisions above、a recipient may use your version of this file under
 * the terms of any one of the MPL、the GPL or the LGPL.
 *
 * */
package com.magiciansforest.audio.vst.binaural;

import com.magiciansforest.audio.data.Air;
import com.magiciansforest.audio.datatype.hrtf.HRTF;
import com.magiciansforest.audio.filter.fft.binauralfilter.BinauralFilter;
import java.io.File;
import java.io.IOException;
import jvst.wrapper.VSTPluginAdapter;

/**
 *
 * @author N_H <h.10x64@gmail.com>
 */
public class Binaural extends VSTPluginAdapter {

    /** paramのインデックス(迎角) */
    private static final int ELEV = 0;
    /** paramのインデックス(方位角) */
    private static final int AZIM = 1;
    /** paramのインデックス(距離) */
    private static final int DIST = 2;
    /** paramのインデックス(ボリューム) */
    private static final int GAIN = 3;
    /** 計算に使うパラメータ */
    private float[] param = new float[]{
        0.0f, //elev
        0.0f, //azim
        1.0f, //dist
        0.0f, //gain
    };
    private HRTF hrtf;
    private Air air;
    private BinauralFilter binauralFilter;
    private double[] dataBuf;
    private BinauralGUI gui = null;

    public Binaural(long Wrapper) {
        super(Wrapper);
        try {
            setFilter();
        } catch (IOException ioe) {
            log("IOException Occured");
            log(ioe.toString());
        }
    }

    //2.0
    public int canDo(String feature) {
        if (feature.equals(CANDO_PLUG_1_IN_1_OUT)) {
            return CANDO_YES;
        } else if (feature.equals(CANDO_PLUG_1_IN_2_OUT)) {
            return CANDO_YES;
        } else if (feature.equals(CANDO_PLUG_2_IN_2_OUT)) {
            return CANDO_YES;
        } else if (feature.equals(CANDO_PLUG_PLUG_AS_CHANNEL_INSERT)) {
            return CANDO_YES;
        } else if (feature.equals(CANDO_PLUG_PLUG_AS_SEND)) {
            return CANDO_YES;
        }
        return CANDO_NO;
    }

    public int getPlugCategory() {
        return PLUG_CATEG_EFFECT;
    }

    public String getProductString() {
        return "binaural";
    }

    public String getProgramNameIndexed(int category, int index) {
        //???
        return "category(" + category + "), index(" + index + ")";
    }

    public String getVendorString() {
        return "n_h <h.10x64@gmail.com>";
    }

    public boolean setBypass(boolean bln) {
        return false;
    }

    public boolean string2Parameter(int i, String valStr) {
        try {
            float val = Float.parseFloat(valStr);
            param[i] = val;
            return true;
        } catch (NumberFormatException nfe) {
            return false;
        }
    }

    //1.0
    public int getNumParams() {
        return param.length - 1;
    }

    public int getNumPrograms() {
        return 1;
    }

    public float getParameter(int i) {
        switch (i) {
            case ELEV:
                return param[i] / 180.0f + 0.5f;
            case AZIM:
                return param[i] / 360.0f;
            case DIST:
                return (param[i] - 1.0f) / 9.0f;
            case GAIN:
                return param[i] / 120.0f;
        }
        return 0.0f;
    }

    public String getParameterDisplay(int i) {
        if (i >= 0 && i < param.length) {
            String ret = Float.toString(param[i]);
            if (ret.length() > 6) {
                return ret.substring(0, 6);
            } else {
                return ret;
            }
        } else {
            return "0.0";
        }
    }

    public String getParameterLabel(int i) {
        switch (i) {
            case ELEV:
                return "elev";
            case AZIM:
                return "azim";
            case DIST:
                return "dist";
            case GAIN:
                return "gain";
            default:
                return "param:" + i;
        }
    }

    public String getParameterName(int i) {
        return getParameterLabel(i);
    }

    public int getProgram() {
        return 0;
    }

    public String getProgramName() {
        return "default";
    }

    public void processReplacing(float[][] in, float[][] out, int frames) {
        double[] buf = new double[frames];
        for (int i = 0; i < frames; i++) {
            for (int ch = 0; ch < in.length; ch++) {
                buf[i] += in[ch][i];
            }
        }
        for (int i = 0; i < frames; i++) {
            buf[i] = Math.pow(10, param[GAIN]) * buf[i];
        }

        if (out.length == 1) {
            //Monoaural
            System.arraycopy(buf, 0, out[0], 0, frames);
        } else {
            //Stereo Headphone
            float[][] ret = binauralEffect(buf);
            for (int ch = 0; ch < out.length; ch++) {
                System.arraycopy(ret[ch], 0, out[ch], 0, frames);
            }
        }
    }

    public void setParameter(int i, float f) {
        //i:パラメータのインデックス
        //f:0.0 - 1.0の値
        switch (i) {
            case ELEV:
                param[i] = (f - 0.5f) * 180.0f;
                if (gui != null) {
                    gui.getSoundEnvironment().setSourcePosition(new Spherical(Math.toRadians(param[ELEV]), Math.toRadians(param[AZIM]), param[DIST]));
                } else {
                    setSoundPosition(param[ELEV], param[AZIM], param[DIST]);
                }
                break;
            case AZIM:
                param[i] = f * 360.0f;
                if (gui != null) {
                    gui.getSoundEnvironment().setSourcePosition(new Spherical(Math.toRadians(param[ELEV]), Math.toRadians(param[AZIM]), param[DIST]));
                } else {
                    setSoundPosition(param[ELEV], param[AZIM], param[DIST]);
                }
                break;
            case DIST:
                param[i] = f * 9.0f + 1.0f;
                if (gui != null) {
                    gui.getSoundEnvironment().setSourcePosition(new Spherical(Math.toRadians(param[ELEV]), Math.toRadians(param[AZIM]), param[DIST]));
                } else {
                    setSoundPosition(param[ELEV], param[AZIM], param[DIST]);
                }
                break;
            case GAIN:
                param[i] = f * 120.0f;
                setVolume(param[GAIN]);
                break;
        }
    }

    public void setProgram(int i) {
        //NOP
    }

    public void setProgramName(String string) {
        //NOP
    }

    public synchronized void setSoundPosition(double elev, double azim, double dist) {
        if (binauralFilter != null) {
            binauralFilter.setSoundPosition_s(elev, azim, dist);
        }
    }

    public void setVolume(float gain) {
        param[GAIN] = gain;
    }

    public void setFilter() throws IOException {
        hrtf = HRTF.readHRTF(new File("./HRTF/MIT.hrtf"));
        air = new Air();
        System.out.println("Author: " + hrtf.getAuthor());
        System.out.println("Copyright: " + hrtf.getCopyright());
        System.out.println("License: " + hrtf.getLicense());
        binauralFilter = new BinauralFilter(hrtf, air, hrtf.getSamplingFrequency());
        log("hrtf: " + hrtf.getCopyright());
    }

    public void setGUI(BinauralGUI gui) {
        this.gui = gui;
    }

    public void setHRTF(HRTF hrtf) {
        //binauralFilter.setHRTF(hrtf);
    }

    private synchronized float[][] binauralEffect(double[] data) {
        if (binauralFilter != null) {
            if (dataBuf == null) {
                dataBuf = new double[2 * data.length];
            } else if (dataBuf.length != 2 * data.length) {
                dataBuf = new double[2 * data.length];
            }

            binauralFilter.filter(dataBuf, data, 1, 2);

            float[][] ret = new float[2][data.length];
            for (int ch = 0; ch < ret.length; ch++) {
                for (int i = 0; i < data.length; i++) {
                    ret[ch][i] = (float) Math.max(-1.0, Math.min(1.0, dataBuf[ret.length * i + ch]));
                }
            }

            if (gui != null) {
                double dt = (double) data.length / 44100.0;
                gui.update(dt);
            }

            return ret;
        } else {
            return new float[2][data.length];
        }
    }
}
